package com.aspectj.core;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;

import com.aspectj.anno.After;
import com.aspectj.anno.AfterReturning;
import com.aspectj.anno.AfterThrowing;
import com.aspectj.anno.Around;
import com.aspectj.anno.Before;
import com.aspectj.anno.Pointcut;
import com.aspectj.param.AspectBean;
import com.aspectj.param.AspectType;
import com.aspectj.param.JoinPoint;
import com.aspectj.param.JoinPoint.Signature;
import com.aspectj.param.ProceedingJoinPoint;

public class AopProxy implements InvocationHandler {

	private Object target;
	private Map<AspectType, AspectBean> aspectBeansMap;

	public AopProxy(Object target, Map<AspectType, AspectBean> aspectBeansMap) {
		super();
		this.target = target;
		this.aspectBeansMap = aspectBeansMap;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		AspectBean match = matchPointCut(target);
		if(match!=null) {
			String methodName = match.getMethod().getName().concat("()");
			AspectBean aspectBeanAround=matchAspect(AspectType.AROUND, methodName);
			if(aspectBeanAround!=null) {
				Object result = null;
				ProceedingJoinPoint pjp=new ProceedingJoinPoint(new Signature(method), args, target,proxy);
				result = invokeAspect(pjp, null,aspectBeanAround);
				return result;
			}else {
				Object result = doNotAround(proxy, method, args, methodName);
				return result;
			}
		}
		return method.invoke(target, args);
	}

	private Object doNotAround(Object proxy, Method method, Object[] args, String methodName)
			throws IllegalAccessException, InvocationTargetException {
		JoinPoint joinPoint=new JoinPoint(new Signature(method), args, target,proxy);
		AspectBean aspectBeanBefore=matchAspect(AspectType.BEFORE, methodName);
		if(aspectBeanBefore!=null) {
			invokeAspect(joinPoint,null, aspectBeanBefore);
		}
		Object result = null;
		try {
			result = method.invoke(target, args);
			AspectBean aspectBeanAfterReturn=matchAspect(AspectType.AFTER_RETURNING, methodName);
			if(aspectBeanAfterReturn!=null) {
				invokeAspect(joinPoint,null, aspectBeanAfterReturn);
			}
			AspectBean aspectBeanAfter=matchAspect(AspectType.AFTER, methodName);
			if(aspectBeanAfter!=null) {
				invokeAspect(joinPoint,null, aspectBeanAfter);
			}
		} catch (Exception e) {
			e.printStackTrace();
			AspectBean aspectBeanAfterThrowing=matchAspect(AspectType.AFTER_THROWING, methodName);
			if(aspectBeanAfterThrowing!=null) {
				invokeAspect(joinPoint, e, aspectBeanAfterThrowing);
			}
		}
		return result;
	}

	private Object invokeAspect(JoinPoint joinPoint,Exception e, AspectBean aspectBeanBefore)
			throws IllegalAccessException, InvocationTargetException {
		Method aspectMethod = aspectBeanBefore.getMethod();
		Class<?>[] paramsClasses = aspectMethod.getParameterTypes();
		Object obj =null;
		if(paramsClasses!=null&&paramsClasses.length>0) {
			Object[] param=new Object[paramsClasses.length];
			for (int i = 0; i < paramsClasses.length; i++) {
				if(paramsClasses[i].getName().equals(JoinPoint.class.getName())) {
					param[i]=joinPoint;
				}else if(paramsClasses[i].getName().equals(ProceedingJoinPoint.class.getName())) {
					param[i]=joinPoint;
				}else if(paramsClasses[i].getName().equals(Exception.class.getName())) {
					param[i]=e;
				} else {
					param[i]=null;
				}
			}
			obj = aspectMethod.invoke(aspectBeanBefore.getAspectBean(),param);
		}else {
			obj = aspectMethod.invoke(aspectBeanBefore.getAspectBean());
		}
		return obj;
	}

	private AspectBean matchPointCut(Object target) {
		Class<?> targetClass = target.getClass();
		AspectBean aspectBean = aspectBeansMap.get(AspectType.POINTCUT);
		Pointcut pointcut=(Pointcut) aspectBean.getAnnotation();
		if(pointcut.value().equals(targetClass.getInterfaces()[0].getPackage().getName())) {
			return aspectBean;
		}
		return null;
	}
	
	private AspectBean matchAspect(AspectType aspectType,String methodName) {
		AspectBean aspectBean = aspectBeansMap.get(aspectType);
		if(aspectBean!=null) {
			Annotation annotation = aspectBean.getAnnotation();
			if(annotation instanceof Before) {
				Before before=(Before) annotation;
				if(before.value().equals(methodName)) {
					return aspectBean;
				}
			}else if(annotation instanceof After) {
				After after=(After) annotation;
				if(after.value().equals(methodName)) {
					return aspectBean;
				}
			}else if(annotation instanceof AfterThrowing) {
				AfterThrowing afterThrowing=(AfterThrowing) annotation;
				if(afterThrowing.value().equals(methodName)) {
					return aspectBean;
				}
			}else if(annotation instanceof AfterReturning) {
				AfterReturning afterReturning=(AfterReturning) annotation;
				if(afterReturning.value().equals(methodName)) {
					return aspectBean;
				}
			}else if(annotation instanceof Around) {
				Around around=(Around) annotation;
				if(around.value().equals(methodName)) {
					return aspectBean;
				}
			}
		}
		return null;
	}
	public static Object newProxyInstance(Object bean, Map<AspectType, AspectBean> aspectBeansMap) {
		Class<?> targetClass = bean.getClass();
		return Proxy.newProxyInstance(targetClass.getClassLoader(), targetClass.getInterfaces(),
				new AopProxy(bean, aspectBeansMap));
	}

}
